﻿namespace StyleCop.CSharp
{
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class TestFlowGraph
    {
        [TestMethod]
        public void Empty_method_gives_empty_flow_graph()
        {
            FlowGraphTester.Test(
@"public void Method()
{
}")
                .Vertex<Method>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Simple_statements_are_OK()
        {
            FlowGraphTester.Test(
@"public void Method()
{
    Method1();
    Method2();
}")
                .MoveToNextVertex()
                .Vertex<ExpressionStatement>()
                .MoveToNextVertex()
                .Vertex<MethodInvocationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>()
                .MoveToNextVertex()
                .Vertex<ExpressionStatement>()
                .MoveToNextVertex()
                .Vertex<MethodInvocationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>()
                .IsAtEnd();
        }

        [TestMethod]
        public void If_with_true_part_is_OK()
        {
            ICodeUnit ifCondtion = null;
            FlowGraphTester.Test(
@"public void Method()
{
    if (true)
    {
    }

    return;
}")
                .MoveToNextVertex()
                .Vertex<IfStatement>()
                .MoveToNextVertex(0)
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("true", expression.Text))
                .Vertex(vertex => ifCondtion = vertex)
                .OutDegree(2)
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>()
                .MoveToNextVertex()
                .Vertex<ReturnStatement>()
                .IsAtEnd()
                .MoveToNextVertex(ifCondtion, 1)
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void If_with_true_and_false_parts_is_OK()
        {
            ICodeUnit ifCondition = null;
            ICodeUnit elseStatement = null;
            FlowGraphTester.Test(
@"public void Method()
{
    if (true)
    {
    }
    else
    {
    }

    return;
}")
                .MoveToNextVertex()
                .Vertex<IfStatement>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("true", expression.Text))
                .OutDegree(2)
                .Vertex(vertex => ifCondition = vertex)
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>()
                .MoveToNextVertex()
                .Vertex<ElseStatement>(statement => elseStatement = statement)
                .OutDegree(2)
                /* The first path takes us through the else. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(statement => Assert.AreEqual(7, statement.LineNumber))
                /* We then move out of the else. */
                .MoveToNextVertex()
                .Vertex<ReturnStatement>()
                /* The second path from the else skips the else. */
                .MoveToNextVertex(elseStatement, 1)
                .Vertex<ReturnStatement>();
        }

        [TestMethod]
        public void If_else_if_works()
        {
            ICodeUnit ifCondition = null;
            ICodeUnit firstElseStatement = null;
            ICodeUnit firstElseCondition = null;
            ICodeUnit secondElseStatement = null;
            FlowGraphTester.Test(
@"public void Method()
{
    if (true)
    {
    }
    else if (false)
    {
    }
    else
    {
    }
}")
                .MoveToNextVertex()
                .Vertex<IfStatement>()
                /* The true contition... */
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("true", expression.Text))
                .Vertex(codeUnit => ifCondition = codeUnit)
                /* The if condition has two path from it. */
                .OutDegree(2)
                /* The first path is the block statement. This is the last statement on the path. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>()
                .MoveToNextVertex()
                .Vertex<ElseStatement>(statement => firstElseStatement = statement)
                /* The second path is the else part of the statement. */
                .MoveToNextVertex(ifCondition, 1)
                .Vertex<ElseStatement>(statement => Assert.AreSame(firstElseStatement, statement))
                /* There are two paths out of the else statement. */
                .OutDegree(2)
                /* The first path is the condition followed by the block. */
                .MoveToNextVertex(0)
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("false", expression.Text))
                .Vertex(codeUnit => firstElseCondition = codeUnit)
                /* The first else condtion has two code paths. */
                .OutDegree(2)
                /* The first path is the body of the else followed by the second else. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>()
                .MoveToNextVertex()
                .Vertex<ElseStatement>(statement => secondElseStatement = statement)
                /* The second path from the first else condition takes us to the second else. */
                .MoveToNextVertex(firstElseCondition, 1)
                .Vertex<ElseStatement>(statement => Assert.AreSame(secondElseStatement, statement));
        }

        [TestMethod]
        public void Return_statement_has_no_last_vertices()
        {
            FlowGraphTester.Test(
@"public void Method()
{
    return;
    Method();
}")
                .MoveToNextVertex()
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Return_statement_with_value_has_no_last_vertices()
        {
            FlowGraphTester.Test(
@"public void Method()
{
    return 5;
    Method();
}")
                .MoveToNextVertex()
                .Vertex<ReturnStatement>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Try_catch_statement_work()
        {
            TryStatement tryStatement = null;
            CatchStatement firstCatchStatement = null;
            ICodeUnit firstCatchExpression = null;
            CatchStatement secondCatchStatement = null;
            FlowGraphTester.Test(
@"public void Method()
{
    try
    {
    }
    catch (Exception ex)
    {
    }
    catch
    {
    }

    return;
}")
                .MoveToNextVertex()
                .Vertex<TryStatement>(statement => tryStatement = statement)
                /* There are two paths from the try statement. */
                .OutDegree(2)
                /* The first path is the block statement followed by the catch statement. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(blockStatement => Assert.AreEqual(4, blockStatement.LineNumber))
                .MoveToNextVertex()
                .Vertex<CatchStatement>(statement => firstCatchStatement = statement)
                /* The second path bypasses the try and goes directly to the catch statement. */
                .MoveToNextVertex(tryStatement, 1)
                .Vertex<CatchStatement>(statement => Assert.AreSame(firstCatchStatement, statement))
                /* There are two paths from the catch statement. */
                .OutDegree(2)
                /* The first path is the catch expression "(Exception ex)". */
                .MoveToNextVertex(0)
                .Vertex<VariableDeclarationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("Exception", expression.Text))
                .MoveToNextVertex()
                .Vertex<VariableDeclaratorExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("ex", expression.Text))
                .Vertex(codeUnit => firstCatchExpression = codeUnit)
                /* There are two code paths from the catch expression. */
                .OutDegree(2)
                /* The first code path is the embedded statements followed by the second catch. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(statement => Assert.AreEqual(7, statement.LineNumber))
                .MoveToNextVertex()
                .Vertex<CatchStatement>(statement => secondCatchStatement = statement)
                /* The second code path skips the embedded statements. */
                .MoveToNextVertex(firstCatchExpression, 1)
                .Vertex<CatchStatement>(statement => Assert.AreSame(secondCatchStatement, statement))
                /* The last catch has two code paths. */
                .OutDegree(2)
                /* The first path is through the embedded statements. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(statement => Assert.AreEqual(10, statement.LineNumber))
                .MoveToNextVertex()
                .Vertex<ReturnStatement>()
                /* The second path skips the embedded statements. */
                .MoveToNextVertex(secondCatchStatement, 1)
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Try_finally_statement_work()
        {
            TryStatement tryStatement = null;
            FinallyStatement finallyStatement = null;
            FlowGraphTester.Test(
@"public void Method()
{
    try
    {
    }
    finally
    {
    }

    return;
}")
                .MoveToNextVertex()
                .Vertex<TryStatement>(vertex => tryStatement = vertex)
                /* The try statement has two code paths. */
                .OutDegree(2)
                /* The first path is the try embedded statement. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(blockStatement => Assert.AreEqual(4, blockStatement.LineNumber))
                .MoveToNextVertex()
                .Vertex<FinallyStatement>(statement => finallyStatement = statement)
                /* The second path skips the try embedded statement and goes to the finally. */
                .MoveToNextVertex(tryStatement, 1)
                .Vertex<FinallyStatement>(statement => Assert.AreSame(finallyStatement, statement))
                .MoveToNextVertex()
                .Vertex<BlockStatement>()
                .MoveToNextVertex()
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Switch_statements_with_multiple_cases_work()
        {
            LiteralExpression switchItem = null;
            FlowGraphTester.Test(
@"public void Method(int parameter)
{
    switch (parameter)
    {
        case 1:
            break;
        case 2:
            break;
    }

    return;
}")
                /* The first statement is the switch statement. */
                .MoveToNextVertex()
                .Vertex<SwitchStatement>()
                /* The next is the switch item. This is the vertex everything else is flows from. */
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("parameter", literalExpression.Text))
                .Vertex<LiteralExpression>(literalExpression => switchItem = literalExpression)
                /* There should be a path for each of the cases as well as bypassing the switch entirely. */
                .OutDegree(3)
                /* Case 1, followed by the break statement and then the return statement. */
                .MoveToNextVertex(0)
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("1", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<BreakStatement>(breakStatement => Assert.AreEqual(6, breakStatement.LineNumber))
                .MoveToNextVertex()
                .Vertex<ReturnStatement>(returnStatement => Assert.AreEqual(11, returnStatement.LineNumber))
                /* Case 2, followed by the break statement and then the return statement. */
                .MoveToNextVertex(switchItem, 1)
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("2", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<BreakStatement>(breakStatement => Assert.AreEqual(8, breakStatement.LineNumber))
                .MoveToNextVertex()
                .Vertex<ReturnStatement>(returnStatement => Assert.AreEqual(11, returnStatement.LineNumber))
                /* Bypass the switch since none of the cases matches and go directly to the return statement. */
                .MoveToNextVertex(switchItem, 2)
                .Vertex<ReturnStatement>(returnStatement => Assert.AreEqual(11, returnStatement.LineNumber))
                .IsAtEnd();
        }

        [TestMethod]
        public void Switch_statements_with_default_work()
        {
            LiteralExpression switchItem = null;
            FlowGraphTester.Test(
@"public void Method(int parameter)
{
    switch (parameter)
    {
        case 1:
            break;
        default:
            break;
    }

    return;
}")
                /* The first statement is the switch statement. */
                .MoveToNextVertex()
                .Vertex<SwitchStatement>()
                /* The next is the switch item. This is the vertex everything else is flows from. */
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("parameter", literalExpression.Text))
                .Vertex<LiteralExpression>(literalExpression => switchItem = literalExpression)
                /* There should be a path for the case and the default. There is no bypass path. */
                .OutDegree(2)
                /* Default, followed by the break statement and then the return statement. */
                .MoveToNextVertex(0)
                .Vertex<BreakStatement>(breakStatement => Assert.AreEqual(8, breakStatement.LineNumber))
                .MoveToNextVertex()
                .Vertex<ReturnStatement>(returnStatement => Assert.AreEqual(11, returnStatement.LineNumber))
                /* Case 1, followed by the break statement and then the return statement. */
                .MoveToNextVertex(switchItem, 1)
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("1", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<BreakStatement>(breakStatement => Assert.AreEqual(6, breakStatement.LineNumber))
                .MoveToNextVertex()
                .Vertex<ReturnStatement>(returnStatement => Assert.AreEqual(11, returnStatement.LineNumber))
                .IsAtEnd();
        }

        [TestMethod]
        public void Foreach_works()
        {
            ICodeUnit enumerablePart = null;
            ICodeUnit forBody = null;
            FlowGraphTester.Test(
@"public void Method()
{
    foreach (var x in y)
    {
    }

    return;
}")
                .MoveToNextVertex()
                /* The foreach is the first statement. */
                .Vertex<ForeachStatement>()
                .MoveToNextVertex()
                .Vertex<VariableDeclarationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("var", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<VariableDeclaratorExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("x", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("y", literalExpression.Text))
                .Vertex(literalExpression => enumerablePart = literalExpression)
                .OutDegree(2)
                /* The first path is through the for body. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(blockStatement => forBody = blockStatement)
                .OutDegree(2)
                /* The first path loops back to the block statement. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(blockStatement => Assert.AreSame(forBody, blockStatement))
                /* The second path exits the loop. */
                .MoveToNextVertex(forBody, 1)
                .Vertex<ReturnStatement>()
                /* The second path from the enumerable part exits the loop. */
                .MoveToNextVertex(enumerablePart, 1)
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void For_works()
        {
            ICodeUnit conditionVertex = null;
            FlowGraphTester.Test(
@"public void Method()
{
    for (int i = 0; i < 99; i++)
    {
    }

    return;
}")
                .MoveToNextVertex()
                /* The for statement should be first. */
                .Vertex<ForStatement>()
                .MoveToNextVertex()
                /* The int i = 0; */
                .Vertex<VariableDeclarationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("int", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<VariableDeclaratorExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("i", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("0", literalExpression.Text))
                .MoveToNextVertex()
                /* The i < 99. */
                .Vertex<RelationalExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("i", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("99", literalExpression.Text))
                .OutDegree(2)
                .Vertex(codeUnit => conditionVertex = codeUnit)
                .MoveToNextVertex(0)
                /* The statements in the for. */
                .Vertex<BlockStatement>()
                .MoveToNextVertex()
                /* The i++. */
                .Vertex<IncrementExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("i", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<RelationalExpression>()
                /* Move back to the end of the conditions and take the second branch. */
                .MoveToNextVertex(conditionVertex, 1)
                /* Take us out of the for statement. */
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Assignment_expressions_have_the_expression_first()
        {
            FlowGraphTester.Test(
@"public void Method(string parameter1, string parameter2)
{
    parameter2 = parameter1 + ""A"";
}")
                .MoveToNextVertex()
                .Vertex<ExpressionStatement>()
                .MoveToNextVertex()
                .Vertex<AssignmentExpression>()
                .MoveToNextVertex()
                /* The right hand side is first. */
                .Vertex<ArithmeticExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("parameter1", expression.Text))
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("\"A\"", expression.Text))
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("parameter2", expression.Text));
        }

        [TestMethod]
        public void While_statements_work()
        {
            ICodeUnit conditionExpression = null;
            ICodeUnit whileBody = null;
            FlowGraphTester.Test(
@"public void Method()
{
    while (true)
    {
    }

    return;
}")
                .MoveToNextVertex()
                .Vertex<WhileStatement>()
                /* The condition is next. */
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("true", expression.Text))
                .Vertex(codeUnit => conditionExpression = codeUnit)
                /* The condition has two code paths. */
                .OutDegree(2)
                /* The first path is the body of the while. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(statement => whileBody = statement)
                /* The body has two code paths. */
                .OutDegree(2)
                /* The first is the while condition. */
                .MoveToNextVertex(0)
                .Vertex<LiteralExpression>(expression => Assert.AreSame(expression, conditionExpression))
                /* The second is the return statement. */
                .MoveToNextVertex(whileBody, 1)
                .Vertex<ReturnStatement>()
                /* The second code path from the codition is the return statement. */
                .MoveToNextVertex(conditionExpression, 1)
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Do_while_statements_work()
        {
            BlockStatement embeddedStatement = null;
            LiteralExpression conditionExpression = null;
            FlowGraphTester.Test(
@"public void Method()
{
    do
    {
    }
    while (true);

    return;
}")
                .MoveToNextVertex()
                /* The do should be the first statement. */
                .Vertex<DoWhileStatement>()
                /* It should be followed by the block statement of the do statement. */
                .MoveToNextVertex()
                .Vertex<BlockStatement>(statement => embeddedStatement = statement)
                /* The condition should be next. */
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("true", expression.Text))
                .Vertex<LiteralExpression>(expression => conditionExpression = expression)
                /* The condition should have two paths from it. */
                .OutDegree(2)
                /* The first path should be back to the embedded statement. */
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>(statement => Assert.AreSame(embeddedStatement, statement))
                /* The second path is out of the loop. */
                .MoveToNextVertex(conditionExpression, 1)
                .Vertex<ReturnStatement>()
                .IsAtEnd();
        }

        [TestMethod]
        public void Lambda_expressions_pass_through_and_around()
        {
            LambdaExpression lambdaExpression = null;
            FlowGraphTester.Test(
@"public int Method()
{
    Method(() => 5);
    return 1;
}")
                .MoveToNextVertex()
                .Vertex<ExpressionStatement>()
                .MoveToNextVertex()
                .Vertex<MethodInvocationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("Method", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<LambdaExpression>(expression => lambdaExpression = expression)
                .OutDegree(2)
                .MoveToNextVertex(0)
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("5", expression.Text))
                .MoveToNextVertex(0)
                .Vertex<ReturnStatement>()
                .MoveToNextVertex(lambdaExpression, 1)
                .Vertex<ReturnStatement>();
        }

        [TestMethod]
        public void Lambda_expressions_with_returns_pass_through_and_around()
        {
            LambdaExpression lambdaExpression = null;
            FlowGraphTester.Test(
@"public int Method()
{
    Method(() => { return 5; });
    return 1;
}")
                .MoveToNextVertex()
                .Vertex<ExpressionStatement>()
                .MoveToNextVertex()
                .Vertex<MethodInvocationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("Method", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<LambdaExpression>(expression => lambdaExpression = expression)
                .OutDegree(2)
                .MoveToNextVertex(0)
                .Vertex<BlockStatement>()
                .MoveToNextVertex()
                .Vertex<ReturnStatement>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("5", expression.Text))
                .IsAtEnd()
                .MoveToNextVertex(lambdaExpression, 1)
                .Vertex<ReturnStatement>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("1", expression.Text))
                .IsAtEnd();
        }

        [TestMethod]
        public void Anonymous_methods_pass_through_and_around()
        {
            AnonymousMethodExpression anonymousMethod = null;
            FlowGraphTester.Test(
@"public int Method()
{
    Method(delegate()
        {
            return 5;
        });
    return 1;
}")
                .MoveToNextVertex()
                .Vertex<ExpressionStatement>()
                .MoveToNextVertex()
                .Vertex<MethodInvocationExpression>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(literalExpression => Assert.AreEqual("Method", literalExpression.Text))
                .MoveToNextVertex()
                .Vertex<AnonymousMethodExpression>(expression => anonymousMethod = expression)
                .OutDegree(2)
                .MoveToNextVertex(0)
                .Vertex<ReturnStatement>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("5", expression.Text))
                .IsAtEnd()
                .MoveToNextVertex(anonymousMethod, 1)
                .Vertex<ReturnStatement>();
        }

        [TestMethod]
        public void Throw_statements_should_terminate_path()
        {
            FlowGraphTester.Test(
@"public int Method()
{
    throw ex;
    return 5;
}")
                .MoveToNextVertex()
                .Vertex<ThrowStatement>()
                .MoveToNextVertex()
                .Vertex<LiteralExpression>(expression => Assert.AreEqual("ex", expression.Text))
                .IsAtEnd();
        }

        [TestMethod]
        public void Rethrow_statements_should_terminate_path()
        {
            FlowGraphTester.Test(
@"public int Method()
{
    throw;
    return 5;
}")
                .MoveToNextVertex()
                .Vertex<ThrowStatement>()
                .IsAtEnd();
        }
    }
}